import { io } from "socket.io-client";
import robot from "robotjs";
import sharp from "sharp";
import { PNG } from "pngjs";
import {
  EMIT_CLIENT_CONFIG_INFO,
  EMIT_CLIENT_IMAGE_INFO,
  EMIT_CONFIG,
  EMIT_INIT,
  EMIT_KEYBOARD,
  EMIT_MOUSE,
  SOCKET_TYPE_CLIENT,
} from "./constrants.js";
import { ackCallback } from "./utils.js";
import { logError, logInfo } from "./log.js";

export function bitmap2Png(obj) {
  const png = new PNG({ width: obj.width, height: obj.height });
  const bitmapData = obj.image;

  for (let y = 0; y < png.height; ++y) {
    for (let x = 0; x < png.width; ++x) {
      const index = (png.width * y + x) << 2; // 计算当前像素索引 // 获取RGB值
      const red = bitmapData[index];
      const green = bitmapData[index + 1];
      const blue = bitmapData[index + 2]; // 写入到PNG对象中
      png.data[index] = red;
      png.data[index + 1] = green;
      png.data[index + 2] = blue;
      png.data[index + 3] = 255; // alpha通道默认为不透明
    }
  }

  return png;
}

export function isEffective(v, defaultValue) {
  if (v === undefined || v === null || v === NaN || v === Infinity) {
    return defaultValue;
  }

  return v;
}

export class ClientSocket {
  timeout = 1000;
  size = 200;
  url = "";
  socket = null;
  connected = false;
  mouseEnable = false;
  keyboardEnable = false;
  imageEnable = false;
  isLooping = false;
  screenWidth = 0;
  screenHeight = 0;

  constructor(url) {
    this.url = url;
    const obj = robot.screen.capture();
    this.screenHeight = obj.height
    this.screenWidth = obj.width
  }

  initConfig() {
    this.mouseEnable = false;
    this.keyboardEnable = false;
    this.imageEnable = false;
    this.timeout = 1000;
    this.size = 200;
  }

  sendMsg = (eventName, data) => {
    return new Promise((resolve, reject) => {
      if (!this.connected) {
        reject("socket don't connected");
        return;
      }

      logInfo("sendmsg", eventName);
      this.socket.timeout(1000).emit(eventName, data, (err, res) => {
        if (err) {
          reject(err);
          return;
        }

        resolve(res);
      });
    });
  };

  onSocketConnect = () => {
    logInfo("client connect");
    this.connected = true;
    this.initConfig();

    this.sendMsg(EMIT_INIT, { type: SOCKET_TYPE_CLIENT })
      .then(() => {
        // this.startLoopSendImage();
        const sendData = {
          screenWidth: this.screenWidth,
          screenHeight: this.screenHeight,
          image: null,
        };

        return this.sendMsg(EMIT_CLIENT_IMAGE_INFO, sendData)
      })
      .catch((err) => {
        logError("init error");
        logError(err);
      });
  };

  onSocketEventMouse = async (arg) => {
    if (!this.mouseEnable) {
      return;
    }
    const type = arg.right ? "right" : "left";

    logInfo("on mouse event", arg);

    if (["down", "up"].includes(arg.type)) {
      robot.mouseToggle(arg.type, type);
    } else if (arg.type === "move") {
      robot.moveMouse(arg.x * this.screenWidth, arg.y * this.screenHeight);
    } else if (arg.type === "click" && type === 'right') {
      robot.moveMouse(arg.x * this.screenWidth, arg.y * this.screenHeight);
      robot.mouseClick(type);
    } else {
      robot.mouseToggle('up', 'left');
    }
  };

  onSocketEventKeyboard = async (arg) => {
    if (!this.keyboardEnable) {
      return;
    }
    
    logInfo("on keyboard event", arg);

    try {
      if (["down", "up"].includes(arg.type)) {
        robot.keyToggle(arg.key, arg.type);
      } else {
        robot.keyTap(arg.key, arg.command);
      }
    } catch (err) {
      logError("key tap error");
      logError(err);
    }
  };

  onSocketEventConfig = async (arg) => {
    logInfo("on config event", arg);

    this.timeout = isEffective(arg.timeout, this.timeout);
    this.size = isEffective(arg.size, this.size);
    this.mouseEnable = isEffective(arg.mouseEnable, this.mouseEnable);
    this.keyboardEnable = isEffective(arg.keyboardEnable, this.keyboardEnable);
    this.imageEnable = isEffective(arg.imageEnable, this.imageEnable);

    if (this.imageEnable) {
      // this.startLoopSendImage();
    }

    return this.sendMsg(EMIT_CLIENT_CONFIG_INFO, {
      mouseEnable: this.mouseEnable,
      keyboardEnable: this.keyboardEnable,
      imageEnable: this.imageEnable,
    });
  };

  connect() {
    if (this.connected) {
      logInfo("client already connected!");
      return;
    }

    this.socket = io(this.url);

    this.socket.on("connect", this.onSocketConnect);

    this.socket.on(EMIT_MOUSE, ackCallback(this.onSocketEventMouse));

    this.socket.on(EMIT_KEYBOARD, ackCallback(this.onSocketEventKeyboard));

    this.socket.on(EMIT_CONFIG, ackCallback(this.onSocketEventConfig));

    this.socket.on("disconnect", () => {
      logInfo("client disconnect");
      this.connected = false;
    });

    this.socket.on("close", () => {
      logInfo("client close");
      this.connected = false;
      this.socket = null;
      this.initConfig();
    });

    this.socket.connect();
  }

  startLoopSendImage() {
    if (this.isLooping) {
      logInfo("isLooping false");
      return;
    }

    if (!this.connected || !this.imageEnable) {
      this.isLooping = false;
      logInfo('loop stop', this.imageEnable, this.connected)
      return;
    }

    if (this._lootTimer) {
      clearTimeout(this._lootTimer);
      this._lootTimer = null;
    }

    this.isLooping = true;
    const startTime = Date.now();
    logInfo("start send image");

    const obj = robot.screen.capture();

    const screenWidth = (this.screenWidth = obj.width);
    const screenHeight = (this.screenHeight = obj.height);

    logInfo("capture time: ", Date.now() - startTime);

    const png = bitmap2Png(obj);

    logInfo("bitmap2png time: ", Date.now() - startTime);

    png
      .pack()
      .pipe(sharp().resize(200))
      .toBuffer((err, buffer) => {
        logInfo("resize time: ", Date.now() - startTime);
        if (err) {
          logError(err);
          return;
        }

        const sendData = {
          screenWidth,
          screenHeight,
          image: buffer,
        };

        this.sendMsg(EMIT_CLIENT_IMAGE_INFO, sendData)
          .then(() => {
            logInfo("EMIT_CLIENT_IMAGE_INFO time: ", Date.now() - startTime);

            this._lootTimer = setTimeout(() => {
              this._lootTimer = null;
              this.isLooping = false;
              // this.startLoopSendImage();
            }, this.timeout);
          })
          .catch((err) => {
            logInfo("EMIT_CLIENT_IMAGE_INFO error");
            logError(err);
            this._lootTimer = null;
            this.isLooping = false;
          });
      });
  }
}

const ss1 = new ClientSocket("ws://192.168.1.25:4000");
ss1.connect();
